#include <bignum.h>
class fPossession: Thing
	theName
	{
		if(!owner) return inherited();
		if(owner == gPlayerChar || owner == gActor) return owner.itPossAdj + ' ' + name;
		else return theNameWithOwner(); 
	}
	owner = location      // default owner is object's location
;

f_isMemberOfList(a, l)  /* returns true if a is a member of the list l */
{
	foreach(local cur in l)
	{
		if(a == cur) return true;
	}
	return nil;
}

class RestrainedArea: Thing
	restrainedDesc = "\^<<theName>> <<verbToBe>> restrained by <<device.theName>>. "
	freeDesc = "\^<<theName>> <<verbToBe>> free. "

	desc
	{
		if(device) restrainedDesc;
		else freeDesc;
	}

	actorRestrainedDesc = nil
	actorFreeDesc = freeDesc
	
	actorDesc
	{
		if(device) actorRestrainedDesc;
		else actorFreeDesc;
	}

	removedevice(actor)   
	{
		local c = device;
		if(c)
		{
			while(c.blocker(location)) c = c.blocker(location);
			nestedActorAction(actor, Doff, c);
		}
	}
	device
	{
		local dev = nil, layer = -1;
		if(location.contents.length()== 0) { return nil;}
		foreach(local cur  in location.contents)
		{
			if(cur.ofKind(Restraint))
			{
				if(cur.restrains(self) && cur.layer > layer && cur.isWorn())
				{
					dev = cur;
					layer = cur.layer;
				}
			}
		}
		return dev;
	}
;

class ForcedPart : fPossession, RestrainedArea, Component
	isSoiled = nil
	isPlugged = nil
	isPainful = nil
	isPierced = nil
	isHole = nil
	piercedDesc = "{It dobj/he} {is} pierced. "
	soiledDesc = "{It dobj/he} {is} filthy. "
	pluggedDesc = "{It dobj/he} {is} plugged. "
	painfulDesc = "{It dobj/he} {is} is sore. "

	desc 
	{
		if(!device) 
		{ 
			freeDesc
			+ (isPierced ? piercedDesc : '')
			+ (isPlugged ? pluggedDesc : '')
			+ (isSoiled ? soiledDesc : '');
		} else {
			inherited();
		}
	}
	
	dobjFor(TortureWith)
	{
		verify()
		{
		}
		check()
		{
			if(!gIobj.ofKind(TortureDevice))
			{
				"Only the most perverted sadists would think of this as a torture device!";
				exit;
			}	
		}
		action()
		{	
			"{You/he} torture{s} <<theName>> with <<gIobj.theName>>. ";
			location.tortureReaction(self,gIobj);
			/*apply pain levels and write victim reaction of owner*/
		}
	}
;

class TortureDevice: Thing
	possibleTargetList = []
	defaultTarget = nil
	painLevel = 0
	damageLevel = 0
	
	iobjFor(TortureWith)
	{
		preCond = [objHeld]
		verify()
		{
		}
		check()
		{
			if(gDobj.ofKind(ForcedPerson))
			{
				if(self.defaultTarget)
				{
					foreach(local b in gDobj.bodyParts)
					{
						if(b.ofKind(self.defaultTarget))
						{
							"(using <<b.theName>> as target)\n";
							replaceAction(TortureWith,b,self);
							return;
						}
					}
				}
				"{You/he} must specify which body part {you/he} want to torture with <<theName>>. ";
				return;
			}
			if(!gDobj.ofKind(ForcedPart))
			{
				"<<gDobj.theName>> cannot be tortured with <<theName>>. ";
				return;
			}
			if(!canBeApplied(gDobj))
			{
				"{You/he} cannot use <<theName>> there. ";
				exit;
			}

		}
		action()
		{
		}
	}
	
	canBeApplied(part)
	{
		foreach(local cur in possibleTargetList)
		{
			if(part.ofKind(cur)) return true;
		}
		return nil;
	}
;

class HitDevice: TortureDevice;

class ElectricDevice: TortureDevice;

class RapeDevice: TortureDevice;

class BurnDevice: TortureDevice;

class PierceDevice: TortureDevice;

class PressureDevice: TortureDevice;

class TortureFurniture: Platform
        initialPainLevel = 0
        initialDamageLevel = 0
        painIncrement = 1
        damageIncrement = 1
	painLevel = 0
	damageLevel = 0
	effectDescription = nil 

        iobjFor(RestrainWith)
	{
		verify() 
		{
            
		}
	}
    
        iobjFor(ReleaseFrom)
        {
                action()
		{
                        "released";
			gDobj.moveInto(location);
		}
        }
    
        increaseLevel()
        {
            painLevel +=painIncrement;
            damageLevel +=damageIncrement;
        }
        decreaseLevel()
        {
            painLevel -=painIncrement;
            if(painLevel<initialPainLevel)
                painLevel=initialPainLevel;
            damageLevel -=damageIncrement;
            if(damageLevel<initialDamageLevel)
                damageLevel = initialDamageLevel;
        }
        resetLevel()
        {
        painLevel = initialPainLevel;
        damageLevel = initialDamageLevel;
        }
        getEffectDescription()
	{
		if(effectDescription)
                    effectDescription.doScript();
	}
    
	iobjFor(TortureWith)
	{
		verify()
		{
		
		}
		check()
		{
			if(!gDobj.ofKind(ForcedPerson))
			{
				"<<gDobj.theName>> cannot be tortured with <<theName>>. ";
				exit;
			}
			if(gDobj.location != self)
			{
				"<<gDobj.theName>> is not in <<theName>>. ";
				exit;
			}
		}
		action()
		{
			"{You/he} torture{s} <<gDobj.theName>> with <<gIobj.theName>>. ";
			gDobj.tortureFurnitureReaction(gIobj);
                        increaseLevel();
		}
	}
	dobjFor(Use)
	{
		verify()
		{
		}
		check()
		{
			foreach(local cur in self.contents)
			{
				if(cur.ofKind(ForcedPerson))
				replaceAction(TortureWith,cur,self);
				return;
			}
		}
		action()
		{
		}
	}
;

class Restraint: LockableWithKey, Wearable 
	layer = 0
	restraintList = []
	autoRemoveOnUnlock = true
	wornDesc = "\^<<location.theName>> <<location.verbToBe>> restrained by <<aName>>. "
	cannotUnlockMessage = "<<theName>> <<verbToBe>> not that easy to open"
	notWornDesc = nil
	doffMsg = "{You/he} remove{s} <<theName>>. "
	wearMsg = "{You/he} put{s} on <<theName>>. "

	hinderFactor = 1.00
	
	actionFailureDesc = " <<theName>> prevents you from doing that "
	actionSuccessDesc = " despite <<theName>> {You/he} succeed{s} "
	
	desc
	{
		if(isWorn())
		{
			wornDesc;
		}
		else if(notWornDesc) notWornDesc;
		else inherited();
	}
	
	actorDesc(actor)
	{
		if(isWornBy(actor)) wornDesc;
	}
	
	intersection(rest)
	{
		local l = [];
		if(rest.restraintList.length() == 0 || restraintList.length() == 0) return l;
		foreach(local cur in rest.restraintList)
		{
			if(f_isMemberOfList(cur, restraintList)) l += cur;
		}
	return l;
	}

	wornover(rest)   /* return true if self must be removed before rest */
	{
		if(intersection(rest) != [] && layer > rest.layer) return true;
		else return nil;
	}

	restrains(c)
	{
		if(c.ofKind(Restraint))
		{
			if(intersection(c) == c.restraintList && layer > c.layer) return true;
		}
		if(c.ofKind(RestrainedArea))
		{
			foreach(local cur in restraintList)
			{
				if(c.ofKind(cur)) return true;
			}
			return nil;
		}
		return nil;
	}
	
	blocker(actor)
	{
		local c = nil, l = -1;
		if(actor.contents.length() == 0) return nil;
		foreach(local cur in actor.contents)
		{
			if(cur.ofKind(Restraint))
			{
				if(cur.wornover(self) && cur.layer > l && cur.isWorn() && cur != self)
				{
					c = cur;
					l = cur.layer;
				}
			}
		}
		return c;
	}

	restraint         
	{
		local c = nil, l = -1;
		if( !isWorn() ) return nil;
		if(location.contents.length() == 0) return nil;
		foreach(local cur in location.contents)
		{
			if(cur.ofKind(Restraint))
			{
				if(cur.restraint(self) && cur.layer > l && cur.isWorn())
				{
					c = cur;
					l = cur.layer;
				}
			}
		}
		return c;
	}

	/* verb handling */
	dobjFor(Wear)
	{
		preCond = [objHeld]
		verify()
		{
			/* make sure the actor isn't already wearing the item */
			if (isWornBy(gActor))
				illogicalAlready(&alreadyWearingMsg);
		}
		check()
		{
			if(blocker(gActor))
			{
				"{You/he} must release <<blocker(gActor).theName>> before {you/he} can put on <<theName>>. ";
				exit;
			}
		}
		action()
		{
			/* make the item worn and describe what happened */
			makeWornBy(gActor);
			wearMsg;
		}
	}
	
	dobjFor(Lock)
	{
		verify()
		{
			/* if we're already locked, there's no point in locking us */
			if (isLocked)
				illogicalAlready(&alreadyLockedMsg);
		}
		action()
		{
			/* perform the generic lock/unlock action processing */
			makeLocked(true);
			defaultReport(&okayLockMsg);

		}
	}

	//override
	lockOrUnlockAction(lock)
	{
	    /* 
	    *   If it's a keyring, let the keyring's action handler do the
	    *   work.  Otherwise, if it's my key, lock/unlock; it's not a
	    *   key, fail.  
	    */
	    if (gIobj.ofKind(Keyring))
	    {
		/* 
		*   do nothing - let the indirect object action handler do
		*   the work 
		*/
	    }
	    else if (keyFitsLock(gIobj))
	    {
		local ko;

		/* 
		*   get the object (us or our master object) that owns the
		*   known key list 
		*/
		ko = getKnownKeyOwner();

		/* 
		*   if the key owner remembers known keys, and it doesn't know
		*   about this working key yet, remember this in the list of
		*   known keys 
		*/
		if (ko.rememberKnownKeys
		    && ko.knownKeyList.indexOf(gIobj) == nil)
		    ko.knownKeyList += gIobj;

		/* set my new state and issue a default report */
		makeLocked(lock);
		if(lock)
		   say(okayLockMsg);
		else
		    say(okayUnlockMsg);
		if(lock == nil && autoRemoveOnUnlock != nil)
		{
		  autoRemoveOnUnlock;
		  doffMsg;
		  makeWornBy(nil);
		  gActor.getDropDestination(self, nil).receiveDrop(self, dropTypeDrop);
		}
	    }
	    else
	    {
		/* the key doesn't work in this lock */
		reportFailure(&keyDoesNotFitLockMsg);
	    }
	}

	iobjFor(RestrainWith)
	{
		preCond = [objHeld]
		verify()
		{
		}
		check()
		{
			if(!gDobj.ofKind(ForcedPerson))
			{
				"{You/he} cannot be restrained with <<theName>>. ";
				return;
			}
			if(blocker(gDobj))
			{
				"{You/he} must release <<blocker(gDobj).theName>> before {you/he} can put on <<theName>>. ";
				exit;
			}

		}
		action()
		{
			/* make the item worn and describe what happened */
			//makeWornBy(gDobj);
			//makeLocked(true);
		}
	}


	dobjFor(Doff)
	{
		preCond = [roomToHoldObj]
		verify()
		{
			/* make sure the actor is actually wearing the item */
			if (!isWorn())
				illogicalAlready(&notWearingMsg);
		}
		check()
		{
			if(blocker(location))
			{
				"{You/he} must release <<blocker(location).theName>> before {you/he} can remove <<theName>>. ";
				exit;
			}
			if(isLocked())
			{
				"{You/he} must unlock <<theName>>. ";
				exit;  
			}
		}
		action()
		{
			/* un-wear the item and describe what happened */
			doffMsg;
			makeWornBy(nil);
			gActor.getDropDestination(self, nil)
				.receiveDrop(self, dropTypeDrop);
		}
	}
;

// =========== Restraints ============

class GagRestraint : Restraint
// Prevents the wearer from speaking
;

class BlindfoldRestraint : Restraint
// Forces the room description to be dark  
;

class HobbleRestraint : Restraint
// Prevents the wearer from leaving the room
;

class HandicapRestraint : Restraint
// Prevents the wearer from touching and acting on other objects
;

class ChastityRestraint : Restraint
// Prevents the wearer from using or touching his/her sex
;

class TortureRestraint : Restraint
        initialPainLevel = 0
        initialDamageLevel = 0
        painIncrement = 1
        damageIncrement = 1
	painLevel = 0
	damageLevel = 0
	effectDescription = nil

        increaseLevel()
        {
            painLevel +=painIncrement;
            damageLevel +=damageIncrement;
        }
        decreaseLevel()
        {
            painLevel -=painIncrement;
            if(painLevel<initialPainLevel)
                painLevel=initialPainLevel;
            damageLevel -=damageIncrement;
            if(damageLevel<initialDamageLevel)
                damageLevel = initialDamageLevel;
        }
        resetLevel()
        {
        painLevel = initialPainLevel;
        damageLevel = initialDamageLevel;
        }
        getEffectDescription()
	{
		if(effectDescription)
                    effectDescription.doScript();
	}
;

class PickableRestraint : Restraint
// Restraint that can be lock-picked using some adequate tool
;

class EscapableRestraint : Restraint
// Restraint that can be escaped from by means of escapology techniques
;

// =========== Verbs ============
DefineTIAction(RestrainWith);
VerbRule(RestrainWith)
	('restrain'|'bind'|'entrap'|'tie') singleDobj 'with' singleIobj
	: RestrainWithAction
	verbPhrase = 'restrain/restraining (what) (with what)'
;

DefineTIAction(RestrainTo);
VerbRule(RestrainTo)
	('restrain'|'bind'|'entrap'|'tie'|'fix'|'strap') singleDobj ('to'|'on') singleIobj
	: RestrainToAction
	verbPhrase = 'restrain/restraining (what) (to what)'
;

DefineTAction(Release);
VerbRule(Release)
	('release'|'free'|'unbind'|'untie'|'unstrap') singleDobj
	: ReleaseAction
	verbPhrase = 'release/releasing (what)'
;
DefineTIAction(ReleaseFrom);
VerbRule(ReleaseFrom)
	('release'|'free'|'unbind'|'untie'|'unstrap') singleDobj ('from'|'off') singleIobj
	: ReleaseFromAction
	verbPhrase = 'release/releasing (what) (from what)'
;


DefineTIAction(TortureWith);
VerbRule(TortureWith)
	('torture'|'hurt'|'torment'|'whip'|'lash'|'burn'|'shock'|'hit') singleDobj 'with' singleIobj
	: TortureWithAction
	verbPhrase = 'torture/torturing (what) (with what)'
;

DefineTAction(Torture);
	VerbRule(Torture)
	('torture'|'hurt'|'torment'|'stretch') singleDobj
	: TortureAction
	verbPhrase = 'torture/torturing (what)'
;

DefineTAction(Use);
	VerbRule(Use)
	('use'|'operate'|'apply') singleDobj
	: UseAction
	verbPhrase = 'use/using (what)'
;

DefineTIAction(PickWith);
	VerbRule(PickWith)
	('pick'|'lockpick'|'lock-pick') singleDobj 'with' singleIobj
	: RestrainWithAction
	verbPhrase = 'pick/picking (what) (with what)'
;

DefineTAction(Escape);
	VerbRule(Escape)
	('escape' 'from'|'struggle' 'out' 'of') singleDobj 
	: RestrainWithAction
	verbPhrase = 'struggle/struggling out of (what)'
;

modify Thing
	dobjFor(RestrainWith)
	{
		verify() 
		{
			illogical('{That dobj/he} {is}n\'t something {you/he} can restrain. ');
		}
	}
	
	iobjFor(RestrainWith)
	{
		verify() 
		{
			illogical('{That iobj/he} do{es}n\'t look very useful as
				a restraint. ');
		}
	}

	dobjFor(TortureWith)
	{
		verify() 
		{
			illogical('{That dobj/he} {is}n\'t something {you/he} can torture. ');
		}
	}
	
	iobjFor(TortureWith)
	{
		verify() 
		{
			illogical('{That iobj/he} do{es}n\'t look very useful as
				a torture device. ');
		}
	}

	dobjFor(PickWith)
	{
		verify() 
		{
			illogical('{That dobj/he} {is}n\'t something {you/he} can pick. ');
		}
	}
	
	iobjFor(PickWith)
	{
		verify() 
		{
			illogical('{That iobj/he} do{es}n\'t look very useful as a lock-picking tool. ');
		}
	}

	dobjFor(Escape)
	{
		verify() 
		{
			illogical('{That dobj/he} {is}n\'t something {you/he} can escape from. ');
		}
	}  
;

// ============ Inventory Lister ==========

modify wornState
	wornName(lst) 
	{
		foreach(local cur in lst)
		{
			if(cur.ofKind(Restraint))
				return 'restraining ' + cur.location.restrainedBy(cur);
		}
		return nil;
	}
;

class restrainedInventoryLister: actorInventoryLister
	showList(pov, parent, lst, options, indent, infoTab, parentGroup)
	{
		/* 
		*   If this is a 'tall' listing, use the normal listing style; for
		*   a 'wide' listing, use our special segregated style.  If we're
		*   being invoked recursively to show a contents listing, we
		*   similarly want to use the base handling. 
		*/
		if ((options & (ListTall | ListContents)) != 0)
		{
			/* inherit the standard behavior */
			inherited(pov, parent, lst, options, indent, infoTab,
				parentGroup);
		}
		else
		{
			local carryingLst, wearingLst, restraintLst;
			local carryingStr, wearingStr, restraintStr;

			/* divide the lists into 'carrying' and 'wearing' sublists */
			carryingLst = new Vector(32);
			wearingLst = new Vector(32);
			restraintLst = new Vector(32);
			foreach (local cur in lst)
				(cur.isWornBy(parent) ? (cur.ofKind(Restraint)? restraintLst: wearingLst) : carryingLst).append(cur);

			/* generate and capture the 'carried' listing */
			carryingStr = outputManager.curOutputStream.captureOutput({:
				carryingLister.showList(pov, parent, carryingLst, options,
							indent, infoTab, parentGroup)});

			/* generate and capture the 'worn' listing */
			wearingStr = outputManager.curOutputStream.captureOutput({:
				wearingLister.showList(pov, parent, wearingLst, options,
						indent, infoTab, parentGroup)});
			
			/* generate and capture the 'restraint' listing */
			restraintStr = outputManager.curOutputStream.captureOutput({:
				wearingLister.showList(pov, parent, restraintLst, options,
						indent, infoTab, parentGroup)});

			/* generate the combined listing */
			showCombinedInventoryList(parent, carryingStr, wearingStr, restraintStr);

			/* 
			*   Now show the out-of-line contents for the whole list, if
			*   appropriate.  We save this until after showing both parts
			*   of the list, to keep the direct inventory parts together
			*   at the beginning of the output.  
			*/
			if ((options & ListRecurse) != 0
				&& indent == 0
				&& (options & ListContents) == 0)
			{
				/* show the contents of each object we didn't list */
				showSeparateContents(pov, lst, options | ListContents,
						infoTab);
			}
		}
	}

	showCombinedInventoryList(parent, carrying, wearing, restrained)
	{
		/* if one or the other sentence is empty, the format is simple */
		if (carrying == '' && wearing == '' && restrained == '')
		{
			/* the parent is completely empty-handed */
			showInventoryEmpty(parent);
		}
		else if (carrying == '')
		{
			/* the whole list is being worn */
			showInventoryWearingOnly(parent, wearing, restrained);
		}
		else if (wearing == '')
		{
			/* the whole list is being carried */
			showInventoryCarryingOnly(parent, carrying, restrained);
		}
		else
		{
			/*
			*   Both listings are populated.  Count the number of
			*   comma-separated or semicolon-separated phrases in each
			*   list.  This will give us an estimate of the grammatical
			*   complexity of each list.  If we have very short lists, a
			*   single sentence will be easier to read; if the lists are
			*   long, we'll show the lists in separate sentences.  
			*/
			if (countPhrases(carrying) + countPhrases(wearing)+ countPhrases(restrained)
				<= singleSentenceMaxNouns)
			{
				/* short enough: use a single-sentence format */
				showInventoryShortLists(parent, carrying, wearing, restrained);
			}
			else
			{
				/* long: use a two-sentence format */
				showInventoryLongLists(parent, carrying, wearing, restrained);
			}
		}
	}

	showInventoryWearingOnly(parent, wearing, restrained)
	{
		/* we're carrying nothing but wearing some items */
		if(restrained == '')
			restrained = 'nothing';
		"\^<<parent.nameIs>> carrying nothing, <<parent.verbToBe>>
		wearing <<wearing != '' ? wearing : 'nothing' >> and <<parent.verbToBe>> restrained by <<restrained>>. ";
	}
	showInventoryCarryingOnly(parent, carrying, restrained)
	{
		if(restrained == '')
			restrained = 'nothing';
		"\^<<parent.nameIs>> carrying <<carrying>>,  and <<parent.verbToBe>> restrained by <<restrained>>. ";
	}
	showInventoryShortLists(parent, carrying, wearing, restrained)
	{
		/* short lists - combine carried and worn in a single sentence */
		if(restrained == '')
			restrained = 'nothing';
		"\^<<parent.nameIs>> carrying <<carrying>>, 
		<<parent.itIsContraction>> wearing <<wearing != '' ? wearing : 'nothing'>>
		and restrained by <<restrained>>. ";
	}
	showInventoryLongLists(parent, carrying, wearing, restrained)
	{
		/* long lists - show carried and worn in separate sentences */
		if(restrained == '')
			restrained = 'nothing';
		"\^<<parent.nameIs>> carrying <<carrying>>.
		\^<<parent.itIsContraction>> wearing <<wearing>> and restrained by <<restrained>>. ";
	}
;

class restrainedHoldingDescInventoryListerLong: restrainedInventoryLister
	showInventoryEmpty(parent)
	{
		
	}
	/*showInventoryWearingOnly(parent, wearing)
	{

		"<.p>\^<<parent.nameIs>> restrained by <<wearing>>. ";
	}
	showInventoryCarryingOnly(parent, carrying)
	{
		
		"<.p>\^<<parent.nameIs>> carrying <<carrying>>. ";
	}
	showInventoryShortLists(parent, carrying, wearing)
	{
		
		"<.p>\^<<parent.nameIs>> carrying <<carrying>>, and
		<<parent.itIsContraction>> restrained by <<wearing>>. ";
	}
	showInventoryLongLists(parent, carrying, wearing)
	{
		
		"<.p>\^<<parent.nameIs>> carrying <<carrying>>.
		\^<<parent.itIsContraction>> restrained by <<wearing>>. ";
	}*/
;
